const LUCIDPG_VERSION = "0.1";

var PGP_SYMMETRIC_KEY_SIZE = [];
PGP_SYMMETRIC_KEY_SIZE[2] = 192 / 8; // TripleDES
PGP_SYMMETRIC_KEY_SIZE[3] = 128 / 8; // CAST5
PGP_SYMMETRIC_KEY_SIZE[4] = 128 / 8; // Blowfish
PGP_SYMMETRIC_KEY_SIZE[7] = 128 / 8; // AES 128
PGP_SYMMETRIC_KEY_SIZE[8] = 192 / 8; // AES 192
PGP_SYMMETRIC_KEY_SIZE[9] = 256 / 8; // AES 256
PGP_SYMMETRIC_KEY_SIZE[10] = 256 / 8; // Twofish

var PGP_SYMMETRIC_BLOCK_SIZE = [];
PGP_SYMMETRIC_BLOCK_SIZE[2] = 64 / 8; // TripleDES
PGP_SYMMETRIC_BLOCK_SIZE[3] = 64 / 8; // CAST5
PGP_SYMMETRIC_BLOCK_SIZE[4] = 64 / 8; // Blowfish
PGP_SYMMETRIC_BLOCK_SIZE[7] = 128 / 8; // AES 128
PGP_SYMMETRIC_BLOCK_SIZE[8] = 128 / 8; // AES 192
PGP_SYMMETRIC_BLOCK_SIZE[9] = 128 / 8; // AES 256
PGP_SYMMETRIC_BLOCK_SIZE[10] = 128 / 8; // Twofish

var PGP_HASH_SIZE = [];
PGP_HASH_SIZE[1] = 128 / 8; // MD5
PGP_HASH_SIZE[2] = 160 / 8; // SHA1
PGP_HASH_SIZE[3] = 160 / 8; // RIPEMD160

var PGP_DERPREFIXES_BY_HASHALGO = [];
PGP_DERPREFIXES_BY_HASHALGO[1] = hex2s("3020300c06082a864886f70d020505000410"); // md5
PGP_DERPREFIXES_BY_HASHALGO[2] = hex2s("3021300906052b0e03021a05000414"); // sha1
PGP_DERPREFIXES_BY_HASHALGO[3] = hex2s("3021300906052b2403020105000414"); // ripemd160

function sfromc(charcode)
{
    return String.fromCharCode(charcode);
}

function pgp_randomByte()
{
    return Math.round(Math.random() * 255) & 255;
};

function s2mpi( s )
{
    var bits = s.length * 8;
    var header = sfromc(bits / 256) + sfromc(bits % 256);
    return header + s;
}

function lucid_gwt(val)
{
    if (val.charCodeAt(0) == 0)
    {
        return val.substr(1);
    }
    else
    {
        lucidapi.error( val.substr(1) );
        throw lucidapi.createThrowable( "luciderr.gwtcode", val.substr(1) );
    }
};

function pgp_hashString(algorithm,input)
{
    //    return lucid_gwt(luciferimpl.getLuciferInstance().hashString(algorithm,input));
    switch (algorithm) {
        case 1:
            return str_md5(input);
        case 2:
            return str_sha1(input);
        case 3:
            return str_rmd160(input);
        default:
            throw lucidapi.createThrowable( "luciderr.unknownHashAlgorithm", algorithm );
    }
};

function pgp_expandString(algorithm,input)
{
    if (algorithm == 0)
        return input;
//    lucidapi.debug("inflating string: %d", algorithm);
    return lucid_gwt(luciferimpl.inflateString(algorithm,input));
};

function pgp_deflateString(algorithm,input)
{
    if (algorithm == 0)
        return input;
//    lucidapi.debug("inflating string: %d", algorithm);
    return lucid_gwt(luciferimpl.deflateString(algorithm,input));
};

function pgp_decryptString(algorithm,input,pgpcfb,key,iv)
{
    return lucid_gwt(luciferimpl.decryptString(pgpcfb,algorithm,input,key,iv ? iv : null));
};

function pgp_encryptString(algorithm,input,pgpcfb,key,iv)
{
    return lucid_gwt(luciferimpl.encryptString(pgpcfb,algorithm,input,key,iv ? iv : null));
};

function randomString(len,nozero)
{
    return lucid_gwt(luciferimpl.randomString(len,nozero));
};

function pgp_updateEntropy(seed)
{
    luciferimpl.updateEntropy(seed & 0x7fffffff);
};

// -----------------------------------------------------
// Compute s**e mod m for RSA public key operation

function pgp_encryptRSA( s, e, m )
{
    return bmodexp(s, e, m);
};

// Compute m**d mod p*q for RSA private key operations.

function pgp_decryptRSA( m, d, p, q, u )
{
    var xp = bmodexp(bmod(m, p), bmod(d, bsub(p, [1])), p);
    var xq = bmodexp(bmod(m, q), bmod(d, bsub(q, [1])), q);

    var t = bsub(xq, xp);
    if (t.length == 0)
    {
        t = bsub(xp, xq);
        t = bmod(bmul(t, u), q);
        t = bsub(q, t);
    }
    else
    {
        t = bmod(bmul(t, u), q);
    }
    return badd(bmul(t, p), xp);
};

function pgp_decryptRSA2( mpis /* m**e mod n */, mpiblob /* dpqu */ )
{
    var locD = mpi2b(mpiblob.rsa_d);
    var locP = mpi2b(mpiblob.rsa_p);
    var locQ = mpi2b(mpiblob.rsa_q);
//    var locU = mpi2b(mpiblob.rsa_u);

    var locC = mpi2b(mpis[0]);
    var locM = bmodexp(locC,locD,bmul(locP,locQ));
    return locM;
};

function pgp_encryptEG( locG, locK, locY, locP, locM )
{
    var locA = bmodexp(locG, locK, locP);
    var locC = bmodexp(locY, locK, locP);
    var locB = bmod(bmul(locM, locC), locP);
    return [ locA, locB ];
};

function pgp_decryptEG( mpis, mpiblob )
{
    var locA = mpi2b(mpis[0]);
    var locB = mpi2b(mpis[1]);

    var locP = mpi2b(mpiblob.eg_p);
    var locX = mpi2b(mpiblob.eg_x);

//    return bmod(bmul(bmodexp(locA, bsub(locP, badd(locX, n2b(1))), locP), locB), locP);
    return bmod(bmul(locB,modinverse(bmodexp(locA,locX,locP),locP)),locP);
};

function pgp_signDSA( locG, locK, locX, locP, locM, locQ, locY )
{
    var locR = bmod(bmodexp(locG, locK, locP), locQ);
    var locS = bmod(bmul(modinverse(locK, locQ),badd(locM,bmul(locX,locR))),locQ);
    return [ locR, locS ];
};

function pgp_verifyDSA( mpiR, mpiS, locG, locP, locM, locQ, locY )
{
    var locR = mpi2b( mpiR );
    var locS = mpi2b( mpiS );
    var locW = modinverse(locS, locQ);
    var  u1 = bmod(bmul(locM,locW),locQ);
    var  u2 = bmod(bmul(locR,locW),locQ);
    u1 = bmodexp(locG,u1,locP);
    u2 = bmodexp(locY,u2,locP);
    var locV = bmod(bmod(bmul(u1,u2),locP),locQ);
    return (b2mpi( locV ) == mpiR);
};

function pgp_encodeTwoOctetLength(len)
{
    var retval = sfromc(Math.floor(len / 256)) + sfromc(len % 256);
    return retval;
}

function pgp_encodeFourOctetNumber(len)
{
    var retval = "";
    retval += sfromc((len >> 24) & 0xFF );
    retval += sfromc((len >> 16) & 0xFF );
    retval += sfromc((len >> 8) & 0xFF );
    retval += sfromc((len) & 0xFF );
    return retval;
}

function pgp_parseFourOctectNumber(s,offset)
{
    if (!offset)
        offset = 0;
    var retval =
        (s.charCodeAt(offset++) << 24)
        + (s.charCodeAt(offset++) << 16)
        + (s.charCodeAt(offset++) << 8)
        + s.charCodeAt(offset++);
    return retval;
}

function pgp_normalizeEOL(text)
{
    if (text.indexOf('\r\n') == -1)
        text = text.replace(/\n/g, '\r\n');
    return text;
};

function pgp_calcDateString(dateObj,extended)
{
    if (typeof(dateObj) == "number")
    {
        var timestamp = dateObj;
        dateObj = new Date();
        dateObj.setTime(timestamp);
    }
    var retval = "";
    retval += dateObj.getFullYear();
    //noinspection JSUnresolvedFunction
    var month = dateObj.getMonth() + 1;
    if (month < 10)
    {
        retval += "-0" + month;
    }
    else
    {
        retval += "-" + month;
    }
    var day = dateObj.getDate();
    if (day < 10)
    {
        retval += "-0" + day;
    }
    else
    {
        retval += "-" + day;
    }
    if (extended)
    {
        retval += lucidapi.sprintf(" %.2d:%.2d:%.2d.%.4d",dateObj.getHours(),dateObj.getMinutes(),dateObj.getSeconds(),dateObj.getMilliseconds())
    }

    return retval;
};

function pgp_calcMPILen( s, offset )
{
    if (!offset)
        offset = 0;
    return Math.floor((s.charCodeAt(offset) * 256 + s.charCodeAt(offset + 1) + 7) / 8);
}


function pgp_insertAnMPI(mpimap,s,offset)
{
    if (!offset)
        offset = 0;
    var len = pgp_calcMPILen( s, offset );
    mpimap.push(s.substr(offset,len + 2));
    return len + 2;
}

function pgp_skipAnMPI(s,offset)
{
    if (!offset)
        offset = 0;
    var len = pgp_calcMPILen( s, offset );
    return len + 2;
}

function pgp_extractMPIs( s )
{
    var ii, len, val = 0;
    var idx = 0;
    var retval = [];
    for (ii = 0; idx < s.length; ii++)
    {
        len = pgp_calcMPILen(s, idx);
        val = mpi2b(s.substr(idx, len + 2));
        retval[ii] = val;
        idx += len + 2;
    }
    return retval;
}

function pgp_detectArmor( text )
{
    var openerIdx = text.indexOf('-----BEGIN PGP ');
    if (openerIdx == -1)
        return null;

    var openerStr = text.substr( openerIdx + '-----BEGIN PGP '.length );
    var openerSfxIdx = openerStr.indexOf( "-----" );

    if (openerSfxIdx == -1)
        return null;

    openerStr = openerStr.substring(0,openerSfxIdx);

    return openerStr;
}

function pgp_armorExtractClearsignedText( text )
{
    var ii = text.indexOf('-----BEGIN PGP ');

    if (ii == -1)
    {
        return text;
    }

    var a = text.indexOf('\n', ii);
    if (a > 0)
        a = text.indexOf('\n', a + 1);

    var e = text.indexOf('\n=', ii);
    if (a > 0 && e > 0)
        text = text.slice(a + 2, e);
    else
    {
        throw new Error('Invalid PGP Message Block');
    }
    return text.substring(0,text.indexOf("\n-----BEGIN PGP"));
}

function pgp_armorStrip( text )
{
    var jj = text.indexOf('-----BEGIN PGP SIGNED MESSAGE-----');
    if (jj > -1)
        text = text.substr('-----BEGIN PGP SIGNED MESSAGE-----'.length);

    var ii = text.indexOf('-----BEGIN PGP ');

    if (ii == -1)
    {
        return text;
    }

    var a = text.indexOf('\n', ii);
    if (a > 0)
        a = text.indexOf('\n', a + 1);

    var e = text.indexOf('\n=', ii);
    if (a > 0 && e > 0)
        text = text.slice(a + 2, e);
    else
    {
        throw new Error('Invalid PGP Message Block');
    }
    return r2s(text);
}

function pgp_crc24( data )
{
    var crc = 0xb704ce;

    for (var n = 0; n < data.length; n++)
    {
        crc ^= (data.charCodeAt(n) & 255) << 16;
        for (var i = 0; i < 8; i++)
        {
            crc <<= 1;
            if (crc & 0x1000000) crc ^= 0x1864cfb;
        }
    }
    return sfromc((crc >> 16) & 255)
        + sfromc((crc >> 8) & 255)
        + sfromc(crc & 255);
}

function pgp_armorWrap( data, header )
{
    var retval = '-----BEGIN PGP ' + header + '-----\n';
    retval += 'Version: LucidPG v' + LUCIDPG_VERSION + '\n\n';
    retval += s2r(data);
    retval += '\n=' + s2r(pgp_crc24(data));
    retval += '\n-----END PGP ' + header + '-----\n';
    return retval;
}

function pgp_decryptCFB(algo,key,ciphertext,iv)
{
    return pgp_decryptString(algo,ciphertext,false,key,iv);
}

function pgp_decryptCFBSpecial(algo,key,ciphertext)
{
    return pgp_decryptString(algo,ciphertext,true,key).substr(18);
}

var PGPPassphraseProvider = lucidapi.AbstractClass.extend(function(classref) { return {
    initialize: function(entries)
    {
        classref.superClass.initialize.call(this);
        this.entries = entries ? entries : {};
    },

    hasKeyId: function( keyid )
    {
        return (keyid in this.entries);
    },

    isEmpty: function()
    {
        var key;
        //noinspection LoopStatementThatDoesntLoopJS
        for (key in this.entries)
        {
            return false;
        }
        return true;
    },

    clear: function()
    {
        this.entries = {};
    },

    persistPassphrase: function( keyid, passphrase )
    {
        this.entries[keyid] = passphrase;
    },

    reallyAskForPassphrase: function( keyid, algo )
    {
        return ["",false];
    },

    askForPassphrase: function( keyid, algo, deferredPersist )
    {
        var result = [this.entries[keyid],false];
        if (!result[0])
            result = this.reallyAskForPassphrase(keyid,algo);
        if (deferredPersist)
        {
            return result;
        }
        else
        {
            if (result[1])
                this.entries[keyid] = result[0];
            return result[0];
        }
    }

}});

var PGPKeySet = lucidapi.AbstractClass.extend(function(classref) { return {
    initialize: function()
    {
        classref.superClass.initialize.call(this);
    },
    getKeyRingData: function( secret )
    {
        var retval = secret ? this.secdata : this.pubdata;
        if (!retval) {
            retval = "";
        }
        return retval;
    },
    setKeyRingData: function(pubdata,secdata,changeop)
    {
        this.pubdata = pubdata;
        this.secdata = secdata;
        return true;
    },
    loadAKeyChain: function(secret)
    {
        try
        {
            var keydata = this.getKeyRingData(secret);
            if (!keydata || keydata.length < 1)
            {
                return [];
            }
            return pgp_loadKeyChain(keydata);
        } catch(e) {
            lucidapi.error(e);
            return [];
        }
    },
    removeKey: function( secret, keyid )
    {
        var retval = {changeop: secret ? "deleteSecret" : "deletePublic"};
        var appendChain = this.loadAKeyChain(secret);
        var pubser = "";
        var donesomething = false;
        for (var ii = 0; ii < appendChain.length; ii++)
        {
            var theKey = appendChain[ii];
            if (theKey.getKeyId() != keyid)
                pubser += theKey.serialize();
            else
                donesomething = true;
        }
        if (!donesomething)
            lucidapi.warn("Keyid scheduled for deletion was not found (noop).");
        retval[ secret ? "newSecData" : "newPubData" ] = pubser;
        return this.setKeyRingData(retval.newPubData,retval.newSecData,retval.changeop);
    },
    importKeyData: function( importdata, dontcopysectopub )
    {
        var data = pgp_armorStrip( importdata );
        var appendChain = pgp_loadKeyChain(importdata);
        var retval = {changeop:"importSecret"};
        if (appendChain.length > 0)
        {
            var oldSecData = this.getKeyRingData(true);
            var oldPubData = this.getKeyRingData(false);

            if (!appendChain[0].isSecretKey())
            {
                retval.newPubData = oldPubData + data;
                retval.changeop = "importPublic";
            }
            else if (dontcopysectopub)
            {
                retval.newSecData = oldSecData + data;
            }
            else
            {
                var pubser = "";
                for (var ii = 0; ii < appendChain.length; ii++)
                {
                    pubser += appendChain[ii].serialize(true);
                }
                retval.newPubData = oldPubData + pubser;
                retval.newSecData = oldSecData + data;
            }
        }
        return this.setKeyRingData(retval.newPubData,retval.newSecData,retval.changeop);
    },
    getSecretChain: function() { return this.loadAKeyChain(true); },
    getPublicChain: function() { return this.loadAKeyChain(false); }
}});

var CodecUtils = {

    pgp_parsePacketLength: function( s, tag, ii )
    {
        var len = 0
    //    if ((tag & 128) == 0)
    //        return null;
        var partial = false;
        if (tag & 64)
        {
            tag &= 63;
            len = s.charCodeAt(ii++);
            if (len > 191 && len < 224)
            {
                len = ((len - 192) << 8) + s.charCodeAt(ii++) + 192;
            }
            else if (len == 255)
            {
                len = (s.charCodeAt(ii++) << 24)
                    + (s.charCodeAt(ii++) << 16)
                    + (s.charCodeAt(ii++) << 8)
                    + s.charCodeAt(ii++);
            }
            else if (len > 223 && len < 255)
            {
                len = (1 << (len & 0x1f));
                partial = true;
            }
        }
        else
        {
            len = tag & 3;
            tag = (tag >> 2) & 15;
            if (len == 0)
            {
                len = s.charCodeAt(ii++);
            }
            else if (len == 1)
            {
                len = (s.charCodeAt(ii++) << 8) + s.charCodeAt(ii++);
            }
            else if (len == 2)
            {
                len = (s.charCodeAt(ii++) << 24)
                    + (s.charCodeAt(ii++) << 16)
                    + (s.charCodeAt(ii++) << 8)
                    + s.charCodeAt(ii++);
            }
            else
            {
                len = s.length - 1;
            }
        }
        return [tag,ii,len,partial];
    },

    pgp_fillS2Kspec: function(s,ii,s2k)
    {
        s2k.mode = s.charCodeAt(ii++);
        s2k.hashAlgo = s.charCodeAt(ii++);
        if (s2k.mode > 0)
        {
            s2k.salt = s.substr(ii,8);
            ii += 8;
        }
        if (s2k.mode == 3)
        {
            var rawcnt = s.charCodeAt(ii++);
            s2k.count = (16 + (rawcnt & 15)) << ((rawcnt >> 4) + 6);
        }
        return ii;
    },

    pgp_calcS2KOutput: function(s2k,passphrase)
    {
        if (s2k.mode == 1)
        {
            return pgp_hashString(s2k.hashAlgo,s2k.salt + passphrase).substr(0,16);
        }
        else if (s2k.mode == 3)
        {
            var hashinput = s2k.salt + passphrase;
            var origlen = hashinput.length
            var count = s2k.count;
            if (count < hashinput.length)
                count = hashinput.length;
            count = count - hashinput.length;
            for (var ii = 0; ii < count; ii++)
            {
                hashinput += hashinput.charAt(ii % origlen)
            }
            return pgp_hashString(s2k.hashAlgo,hashinput).substr(0,16);
        }
        else // type 0
        {
            return pgp_hashString(s2k.hashAlgo,passphrase).substr(0,16);
        }
    },

    pgp_createPacketHeader: function( tag, len, subpacket )
    {
    //        var origlen = len;
        if (subpacket)
            len += 1;
        else
            tag += 0xC0;

        var h = "";
    /*
        if (len <= 191)
        {
            h += sfromc(len);
        }
        else if (len <= 8383)
        {
            len -= 192;
            h += sfromc((len / 256) + 192);
            h += sfromc(len % 256);
        }
        else
        {
    */
            h += sfromc(0xFF);
            h += sfromc((len >> 24) & 0xFF );
            h += sfromc((len >> 16) & 0xFF );
            h += sfromc((len >> 8) & 0xFF );
            h += sfromc((len) & 0xFF );
    //        }
        if (subpacket)
        {
            return h + sfromc(tag);
        }
        else
        {
            return sfromc(tag) + h;
        }
    },

    /* type 1 */
    encodePublicKeyEncryptedSession: function(pubKey,symAlgo,sessionkey)
    {
        var keyId = hex2s(pubKey.getKeyId());
    //    return encodePublicKeyEncryptedSessionHelper(keyId, pubKey.getAlgoType(), symAlgo, sessionkey, pubKey.getMPIBlob());
    //}
    //function encodePublicKeyEncryptedSessionHelper( keyId, keytyp, symAlgo, sessionkey, mpiblob )
    //{
        var keytyp = pubKey.getAlgoType();
        var mpiblob = pubKey.getMPIBlob();
        var el = [3,5,9,17,513,1025,2049,4097];

        var pkeyByteLength = 0;

        var mod = [];
        var exp = [];
        var enc = '';

        if (keytyp == 16 || keytyp == 20) // ElGamal
        {
            pkeyByteLength = pgp_calcMPILen(mpiblob.eg_p);
            mod = mpi2b(mpiblob.eg_p);
            var grp = mpi2b(mpiblob.eg_g);
            var y = mpi2b(mpiblob.eg_y);
            exp[0] = el[pgp_randomByte() & 7];
        }
        else // RSA?
        {
            pkeyByteLength = pgp_calcMPILen(mpiblob.rsa_n);
            mod = mpi2b(mpiblob.rsa_n);
            exp = mpi2b(mpiblob.rsa_e);
        }

        var lsk = sessionkey.length;

        // calculate checksum of session key
        var c = 0;
        for (var i = 0; i < lsk; i++)
            c += sessionkey.charCodeAt(i);
        c &= 0xffff;

        // create MPI from session key using PKCS-1 block type 02
        var lm = (pkeyByteLength - 2) * 8 + 2;
        var m = sfromc(lm / 256) + sfromc(lm % 256) // 2
            + sfromc(2)         // skip leading 0 for MPI // 1
            + randomString(pkeyByteLength - lsk - 6, true) + '\0'   // add random padding (non-zero)  42 + 1
            + sfromc(symAlgo) + sessionkey // 1 + 16
            + sfromc(c / 256) + sfromc(c & 255); // 2

        if (keytyp == 16 || keytyp == 20) // ElGamal
        {
            var B = bmodexp(grp, exp, mod);
            var C = bmodexp(y, exp, mod);
            enc = b2mpi(B) + b2mpi(bmod(bmul(mpi2b(m), C), mod));

            return CodecUtils.pgp_createPacketHeader(1, enc.length + 10)
                + sfromc(3) + keyId + sfromc(16) + enc;
        }
        else // RSA?
        {
            // rsa encrypt the result and convert into mpi
            enc = b2mpi(bmodexp(mpi2b(m), exp, mod));

            return CodecUtils.pgp_createPacketHeader(1, enc.length + 10)
                + sfromc(3) + keyId + sfromc(1) + enc;
        }
    },

    /* type 2 */
    encodeSignature: function( text, keyid, mpis, sigtype, pubkeyalgo )
    {
        var hashableSuffix = "";
        if (!sigtype)
            sigtype = 0;
        hashableSuffix += sfromc(sigtype);

        var timeinsecs = Math.round((new Date()).getTime() / 1000);

        hashableSuffix += pgp_encodeFourOctetNumber(timeinsecs);


        var hash = str_sha1(text + hashableSuffix);

        var builder = sfromc(3); // version
        builder += sfromc(5); // hashdata len
        builder += hashableSuffix;
        builder += hex2s(keyid); // keyid
        builder += sfromc(pubkeyalgo); // pubkey algo
        builder += sfromc(2); // hash algo
        builder += hash.substr(0,2); // left-hand hash bytes
    //    lucidapi.debug("hash.substr(0,2): %d", s2hex(hash.substr(0,2)));

        if (pubkeyalgo == 17)
        {
            return CodecUtils.encodeDSASignature(mpis,builder,hash);
        }
        else
        {
            return CodecUtils.encodeRSASignature(mpis,builder,hash);
        }
    },

    encodeDSASignature: function(mpis,builder,hash)
    {
        var randn = randomString(mpis.dsa_q.length - 2);
        randn = "\0" + sfromc(160) + sfromc(0xFF) + randn.substr(1);
        var randb = mpi2b(randn);
        var dsaqb = mpi2b(mpis.dsa_q);
        while (s2hex(b2mpi(bsub(dsaqb,randb))) == "0000")
        {
            randb = bdiv(randb,[2]).q;
        }
        randn = b2mpi(randb);

        var locK = randb;
        var mpiX = mpis.dsa_x;
        var sigRandS = pgp_signDSA(mpi2b(mpis.dsa_g),locK,mpi2b(mpiX),mpi2b(mpis.dsa_p),mpi2b(s2mpi(hash)),mpi2b(mpis.dsa_q),mpi2b(mpis.dsa_y));

        builder += b2mpi(sigRandS[0]); // mpis
        builder += b2mpi(sigRandS[1]); // mpis

        return CodecUtils.pgp_createPacketHeader(2, builder.length) + builder;
    },

    encodeRSASignature: function(mpis,builder,hash)
    {
        var derprefix = PGP_DERPREFIXES_BY_HASHALGO[2];

        var em = sfromc(0) + derprefix + hash;
        while (em.length < mpis.rsa_n.length - 4)
        {
            em = sfromc(0xFF) + em;
        }
        em = sfromc(0x00) + sfromc(0x01) + em;
        var sigval = bmodexp(mpi2b(s2mpi(em)), mpi2b(mpis.rsa_d), mpi2b(mpis.rsa_n));
        builder += b2mpi(sigval);

        return CodecUtils.pgp_createPacketHeader(2, builder.length) + builder;
    },


    encodeSigTimeSubpacket: function(tag,dateobj)
    {
        var timeinsecs = Math.round((dateobj).getTime() / 1000);
        var pktdata = pgp_encodeFourOctetNumber(timeinsecs);
        return CodecUtils.pgp_createPacketHeader(tag,pktdata.length,true) + pktdata;
    },

    onePassSignature: function( keyid, sigtype, pubkeyalgo )
    {
        if (!sigtype)
            sigtype = 0;

        var builder = sfromc(3); // version
        builder += sfromc(sigtype); // sigtype
        builder += sfromc(2); // hash algo
        builder += sfromc(pubkeyalgo); // pubkey algo
        builder += hex2s(keyid); // keyid
        builder += sfromc(1); // nested (0 == true ?)
        return CodecUtils.pgp_createPacketHeader(4, builder.length) + builder;
    },

    /* type 11 */
    encodeLiteralData: function( text, datatype, filename, tstamp )
    {
        if (!datatype)
            datatype = 'b';
        if (!filename)
            filename = "file";
        if (!tstamp)
        {
            var timeinsecs = Math.round((new Date()).getTime() / 1000);
            tstamp = pgp_encodeFourOctetNumber(timeinsecs);
        }
/*
        switch (datatype) {
            case 't':
                text = pgp_normalizeEOL(text);
                break;
            case 'b':
                break;
        }
*/
        text = datatype + sfromc(filename.length) + filename + tstamp + text;
        return CodecUtils.pgp_createPacketHeader(11, text.length) + text;
    },

    /* type 18 */
    encodeEncryptedIntegrityProtectedData: function( algorithm, key, ldtext )
    {
        var blocksize = PGP_SYMMETRIC_BLOCK_SIZE[algorithm];
        var prefix = randomString( blocksize, false );
        prefix += prefix.charAt( blocksize - 2 );
        prefix += prefix.charAt( blocksize - 1 );
        var hashinput = prefix + ldtext + sfromc(0xD3) + sfromc(0x14);
        var hashresult = pgp_hashString(2,hashinput);
        var encinput = hashinput + hashresult;
        var enc = sfromc(1) + pgp_encryptString( algorithm, encinput, false, key ).substr(0,encinput.length);
        return CodecUtils.pgp_createPacketHeader(18, enc.length) + enc;
    },

    encodeCompressedData: function( algo, outtext )
    {
        var enc = sfromc(algo) + pgp_deflateString(algo,outtext);
//        lucidapi.debug("compressed from %d to %d", outtext.length, enc.length - 1);
        return CodecUtils.pgp_createPacketHeader(8, enc.length) + enc;
    }
};

var PGPPacketTypes = {};

var PGPMetaTypes = {};

PGPPacketTypes.AbstractPacket = lucidapi.AbstractClass.extend(function(classref) { return {
    initialize: function(tag)
    {
        classref.superClass.initialize.call(this);
        this.m_pktTag = tag;
        this.m_pktVersion = 0;
        this.m_pktMpis = [];
    }
/*
    __noSuchMethod__: function(methodId,args)
    {
        throw new Error("Undefined method call: " + methodId);
    }
*/
}});

PGPPacketTypes.StandardPacket = PGPPacketTypes.AbstractPacket.extend(function(classref) { return {
    initialize: function(tag,rawdata)
    {
        classref.superClass.initialize.call(this,tag);
        this.m_pktRawdata = rawdata;
    },
    serialize: function()
    {
        return CodecUtils.pgp_createPacketHeader(this.m_pktTag,this.m_pktRawdata.length) + this.m_pktRawdata;
    }
}});

var PGPSignatureSubpacket = PGPPacketTypes.StandardPacket.extend(function(classref) { return {
    initialize: function(tag,s)
    {
        lucidapi.assert(tag == s.charCodeAt(0));
        classref.superClass.initialize.call(this,tag,s.substr(1));
    }
}});

PGPPacketTypes.SignaturePacket = PGPPacketTypes.StandardPacket.extend(function(classref) { return {
    initialize: function(s)
    {
        classref.superClass.initialize.call(this,2,s);
//        lucidapi.debug("pktlen: %d", s.length);
        var ii = 0;
        this.m_pktVersion = s.charCodeAt(ii++);
        lucidapi.assert(4 == this.m_pktVersion || 3 == this.m_pktVersion);
        if (this.m_pktVersion == 3)
        {
            var miniheadlen = s.charCodeAt(ii++);
            lucidapi.assert(5 == miniheadlen);
            this.m_hashTrailer = s.substr(ii,5);
            this.m_sigType = s.charCodeAt(ii++);
            this.m_createdStr = s.substr(ii,4);
            this.m_created = pgp_parseFourOctectNumber(this.m_createdStr);
            ii += 4;
            this.m_signerId = s.substr(ii, 8);
            ii += 8;
            this.m_sigPubAlgo = s.charCodeAt(ii++);
            this.m_sigHashAlgo = s.charCodeAt(ii++);
        }
        else
        {
            this.m_sigType = s.charCodeAt(ii++);
            this.m_sigPubAlgo = s.charCodeAt(ii++);
    //        lucidapi.debug("type, algo: %d, %d", this.m_sigType,this.m_sigPubAlgo)
            this.m_sigHashAlgo = s.charCodeAt(ii++);

            this.m_subpacketmap = {};

            ii += this.parseSignatureSubpackets(s,ii);
            this.m_hashTrailer = s.substring(0,ii);
            this.m_hashTrailer += sfromc(0x04) + sfromc(0xFF) + pgp_encodeFourOctetNumber(this.m_hashTrailer.length);
    //        lucidapi.debug("starting unhashed");
            ii += this.parseSignatureSubpackets(s,ii);

        }

        this.m_lefthandstr = s.substr(ii,2);
        this.m_lefthand = (s.charCodeAt(ii++) << 8) + s.charCodeAt(ii++);
//        lucidapi.debug("sigPubAlgo: %d", this.m_sigPubAlgo);
        if (this.m_sigPubAlgo == 1 || this.m_sigPubAlgo == 3)
        {
            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            this.m_pktMpis.rsa_z = this.m_pktMpis[this.m_pktMpis.length - 1];
        }
        else if (this.m_sigPubAlgo == 17)
        {
            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            this.m_pktMpis.dsa_r = this.m_pktMpis[this.m_pktMpis.length - 1];

            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            this.m_pktMpis.dsa_s = this.m_pktMpis[this.m_pktMpis.length - 1];
        }
        else
        {
            throw lucidapi.createThrowable( "luciderr.unknownAsymmetricAlgorithm", this.m_sigPubAlgo );
        }
    },
    getTrailer: function()
    {
        return this.m_hashTrailer;
    },

    parseSignatureSubpackets: function(ins,offset)
    {
        var length = (ins.charCodeAt(offset++) << 8) + ins.charCodeAt(offset++);

        var s = ins.substr(offset,length);

        for (var ii = 0; ii < s.length;)
        {
            var len = s.charCodeAt(ii++);
            if (len > 191 && len < 255)
            {
                len = ((len - 192) << 8) + s.charCodeAt(ii++) + 192;
            }
            else if (len == 255)
            {
                len = (s.charCodeAt(ii++) << 24)
                    + (s.charCodeAt(ii++) << 16)
                    + (s.charCodeAt(ii++) << 8)
                    + s.charCodeAt(ii++);
            }

            var tag = s.charCodeAt(ii);

            var packetdata = s.substr(ii,len);
            var packetmapkey = tag.toString(16);
            if (packetmapkey.length < 2)
            {
                packetmapkey = "0" + packetmapkey;
            }
            packetmapkey = "0x" + packetmapkey;

            this.m_subpacketmap[packetmapkey] = new PGPSignatureSubpacket(tag,packetdata);
//            lucidapi.debug("added subpacket %s", packetmapkey );
            switch (packetmapkey) {
                case "0x1b":
                case "0x0b":
                case "0x15":
                case "0x16":
                case "0x1e":
    //                lucidapi.debug("subpacket %s: %s", packetmapkey, s2hex(packetdata.substr(1)));
                    break;
                case "0x10":
    //                lucidapi.debug("subpacket %s: %s", packetmapkey, s2hex(packetdata.substr(1)));
                    break;
            }
            ii += len;
        }

        return length + 2;
    },
    getIssuer: function()
    {
        if (this.m_pktVersion == 3)
        {
            return s2hex(this.m_signerId);
        }
        else
        {
            var subpacket = this.m_subpacketmap["0x10"];
            if (subpacket)
                return s2hex(subpacket.m_pktRawdata);
        }
        return null;
    },
    getKeyExpirationTime: function()
    {
        if (this.m_pktVersion == 4)
        {
            var subpacket = this.m_subpacketmap["0x09"];
            if (subpacket)
                return pgp_parseFourOctectNumber(subpacket.m_pktRawdata);
        }
        return 0;
    },
    getKeyFlags: function()
    {
        if (this.m_pktVersion == 4)
        {
            var subpacket = this.m_subpacketmap["0x1b"];
            if (subpacket)
                return subpacket.m_pktRawdata;
        }
        return null;
    },
    verifySignature: function( text, mpis )
    {
        var hashableSuffix = this.getTrailer();
        switch (this.m_sigType) {
            case 1:
                text = pgp_normalizeEOL(text);
                break;
        }
        var hash = pgp_hashString(this.m_sigHashAlgo,text + hashableSuffix);

        var locM = mpi2b(s2mpi(hash));

        if (this.m_sigPubAlgo == 17)
        {
            var locG = mpi2b(mpis.dsa_g);
            var locP = mpi2b(mpis.dsa_p);
            var locQ = mpi2b(mpis.dsa_q);
            var locY = mpi2b(mpis.dsa_y);

            var mpiR = this.m_pktMpis.dsa_r;
            var mpiS = this.m_pktMpis.dsa_s;

            return pgp_verifyDSA(mpiR, mpiS, locG, locP, locM, locQ, locY);
        }
        else
        {
//            lucidapi.debug("sigpktver: %d", this.m_pktVersion);
            var locZ = mpi2b(this.m_pktMpis.rsa_z);
            var locE = mpi2b(mpis.rsa_e);
            var locN = mpi2b(mpis.rsa_n);
            var dm = bmodexp(locZ, locE, locN);
            var mpiM = b2mpi(dm);
            var chopidx = mpiM.indexOf(sfromc(0xFF));
            chopidx = mpiM.indexOf(sfromc(0x00),chopidx);
            mpiM = mpiM.substr(chopidx + 1);

            var derprefix = PGP_DERPREFIXES_BY_HASHALGO[this.m_sigHashAlgo]
            if (mpiM.indexOf(derprefix) == 0)
            {
                mpiM = mpiM.substr(derprefix.length);
            }
            else
            {
                lucidapi.warn("hashalgo mismatch!");
            }
            return (mpiM == hash);
        }
    }
}});

PGPPacketTypes.CompressedPacket = PGPPacketTypes.StandardPacket.extend(function(classref) { return {
    initialize: function(s)
    {
        classref.superClass.initialize.call(this,8,s);
        var ii = 0;
        this.m_algo = s.charCodeAt(ii++);
        this.m_payload = s.substr(ii);
    },
    getCompressedPayload: function()
    {
        return pgp_expandString(this.m_algo, this.m_payload);
    }
}});


PGPPacketTypes.LiteralPacket = PGPPacketTypes.StandardPacket.extend(function(classref) { return {
    initialize: function(s)
    {
        classref.superClass.initialize.call(this,11,s);
        var ii = 0;
        //noinspection JSUnusedLocalSymbols
        this.m_format = s.charAt(ii++);
        //noinspection UnnecessaryLocalVariableJS
        var namelen = s.charCodeAt(ii++);
        this.m_filename = s.substr(ii,namelen);
        ii += namelen; // skip name
        ii += 4; // skip date
        this.m_payload = s.substr(ii);
    }
}});

PGPPacketTypes.SessionPublicKeyPacket = PGPPacketTypes.StandardPacket.extend(function(classref) { return {
    initialize: function(s)
    {
        classref.superClass.initialize.call(this,1,s);
        var ii = 0;
        this.m_pktVersion = s.charCodeAt(ii++);
        lucidapi.assert(3 == this.m_pktVersion);
        this.m_sessionKeyId = s.substr(ii, 8);
        ii += 8;
        var thealgo = s.charCodeAt(ii++);
        if (thealgo == 1 || thealgo == 2) // RSA
        {
            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
        }
        else if (thealgo == 16 || thealgo == 20) // ElGamal
        {
            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
        }
        else
        {
            throw lucidapi.createThrowable( "luciderr.unknownAsymmetricAlgorithm", thealgo );
        }
    },
    getKeyId: function()
    {
        return s2hex(this.m_sessionKeyId);
    },
    decryptSessionKey: function( passphrase, cryptoKey )
    {
        if (!cryptoKey)
            return null;
        var mpiBlob = cryptoKey.getSecretBlob(passphrase);
        var sessKeyMpi = null;
        if (cryptoKey.mm_pubKeyAlgo < 4)
        {
            sessKeyMpi = b2mpi(pgp_decryptRSA2(this.m_pktMpis, mpiBlob))
        }
        else
        {
            sessKeyMpi = b2mpi(pgp_decryptEG(this.m_pktMpis, mpiBlob))
        }
        for (var jj = 2; jj < sessKeyMpi.length; jj++)
        {
            if (sessKeyMpi.charCodeAt(jj) == 0)
                break;
        }
        var sessionkey = sessKeyMpi.substring(jj + 1, sessKeyMpi.length - 2);

        // calculate checksum of session key
        var c = 0;
        for (var i = 0; i < sessionkey.length; i++)
            c += sessionkey.charCodeAt(i);
        c &= 0xffff;

    /*
        if (sfromc(c / 256) != sessKeyMpi.charAt(sessKeyMpi.length - 2))
            throw new Error("booger");
        if (sfromc(c & 255) != sessKeyMpi.charAt(sessKeyMpi.length - 1))
            throw new Error("booger");
    */

        return sessionkey;
    }
}});

PGPPacketTypes.SessionSymmetricKeyPacket = PGPPacketTypes.StandardPacket.extend(function(classref) { return {
    initialize: function(s)
    {
        classref.superClass.initialize.call(this,3,s);
        var ii = 0;
        this.m_pktVersion = s.charCodeAt(ii++);
        lucidapi.assert(4 == this.m_pktVersion);
        this.m_sessionSymAlg = s.charCodeAt(ii++);
        this.s2k = {};
        ii = CodecUtils.pgp_fillS2Kspec(s,ii,this.s2k);
        this.m_encsesskey = s.substr(ii);
    },
    decryptSymmetricSessionKey: function( passphrase )
    {
        var s2kout = CodecUtils.pgp_calcS2KOutput(this.s2k,passphrase);
        if (this.m_encsesskey.length < 1)
            return sfromc(this.m_sessionSymAlg) + s2kout;
        return pgp_decryptCFB(this.m_sessionSymAlg,s2kout,this.m_encsesskey);
    }
}});

PGPPacketTypes.EncryptedDataPacket = PGPPacketTypes.StandardPacket.extend(function(classref) { return {
    initialize: function(s)
    {
        classref.superClass.initialize.call(this,9,s);
    },
    getEncryptedPayload: function(key,algo)
    {
        var opgp_const_bpbl = 16;

        var retval = pgp_decryptCFBSpecial(algo,key,this.m_pktRawdata);
        var len = this.m_pktRawdata.length - (opgp_const_bpbl + 2);
        return retval.substr(0,len);
    }
}});


PGPPacketTypes.EncryptedIntegrityDataPacket = PGPPacketTypes.StandardPacket.extend(function(classref) { return {
    initialize: function(s)
    {
        classref.superClass.initialize.call(this,18,s);
        var ii = 0;
        var theversion = s.charCodeAt(ii++);
        lucidapi.assert(1 == theversion);
        this.m_ciphertext = s.substr(ii);
    },
    getEncryptedPayload: function(key,algo)
    {
        var blksize = PGP_SYMMETRIC_BLOCK_SIZE[algo];
        var ivsize = blksize + 2;
        var retval = pgp_decryptCFB(algo, key, this.m_ciphertext );
        lucidapi.assert( retval.charCodeAt(blksize - 2) == retval.charCodeAt(blksize));
        lucidapi.assert( retval.charCodeAt(blksize - 1) == retval.charCodeAt(blksize + 1));
        return retval.substr(ivsize, this.m_ciphertext.length - (22 + ivsize));
    }
}});


PGPPacketTypes.PublicKeyPacket = PGPPacketTypes.StandardPacket.extend(function(classref) { return {
    initialize: function(tag,s)
    {
        classref.superClass.initialize.call(this,tag,s);
        var ii = 0;
        var k = ii;
        this.m_pktVersion = s.charCodeAt(ii++);
        //lucdbg.console.assert(3 <= this.m_pktVersion && 4 >= this.m_pktVersion, "Invalid version!");

        this.m_created =
            (s.charCodeAt(ii++) << 24)
            + (s.charCodeAt(ii++) << 16)
            + (s.charCodeAt(ii++) << 8)
            + s.charCodeAt(ii++);

        if (this.m_pktVersion == 2 || this.m_pktVersion == 3)
        {
            this.m_validity = (s.charCodeAt(ii++) << 8) + s.charCodeAt(ii++);
        }

        this.m_algo = s.charCodeAt(ii++);
        this.m_pktMpistr = s.substr(ii);
        var thempis = this.m_pktMpis;
        if (this.m_algo == 1 || this.m_algo == 2 || this.m_algo == 3) // RSA
        {
            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            thempis.rsa_n = thempis[thempis.length - 1];

            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            thempis.rsa_e = thempis[thempis.length - 1];
        }
        else if (this.m_algo == 16 || this.m_algo == 20) // ElGamal
        {
            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            thempis.eg_p = thempis[thempis.length - 1];
//            dump("\neg_p: " + s2hex(thempis.eg_p));

            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            thempis.eg_g = thempis[thempis.length - 1];
//            dump("\neg_g: " + s2hex(thempis.eg_g));

            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            thempis.eg_y = thempis[thempis.length - 1];
        }
        else if (this.m_algo == 17) // DSA
        {
            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            thempis.dsa_p = thempis[thempis.length - 1];

            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            thempis.dsa_q = thempis[thempis.length - 1];

            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            thempis.dsa_g = thempis[thempis.length - 1];

            ii += pgp_insertAnMPI( this.m_pktMpis, s, ii );
            thempis.dsa_y = thempis[thempis.length - 1];
        }
        this.m_pkt_endPubMpiIdx = ii;

        if (this.m_pktVersion >= 4)
        {
            var somefp = pgp_hashString(2,this.getPacketWithAltHeader());
            this.m_fp = s2hex(somefp);
//            lucidapi.debug("fp: " + this.m_fp);
            this.m_keyid = s2hex(somefp.substr(somefp.length - 8, 8));
        }
        else // must be version 3
        {
            var mod = thempis.rsa_n;
            this.m_keyid = s2hex(mod.substr(mod.length - 8, 8));
        }
        this.endingII = ii;
    },
    getPacketWithAltHeader: function()
    {
        var pubpktlen = this.m_pkt_endPubMpiIdx;
//        lucidapi.debug("len: %d %d ", this.m_algo, pubpktlen);
        var enclen = pgp_encodeTwoOctetLength(pubpktlen);
//        lucidapi.debug("enclen: %s", s2hex(enclen));
        var retval = sfromc(0x99) + enclen + this.m_pktRawdata.substr(0, pubpktlen);
        return retval;
    }
}});


PGPPacketTypes.SecretKeyPacket = PGPPacketTypes.PublicKeyPacket.extend(function(classref) { return {
    initialize: function(tag,s)
    {
        classref.superClass.initialize.call(this,tag,s);
        var ii = this.endingII;
        this.m_secretdata = s.substr(ii);
    }
}});

PGPPacketTypes.MetaSignedPacket = PGPPacketTypes.AbstractPacket.extend(function(classref) { return {
    initialize: function(packetToker)
    {
        classref.superClass.initialize.call(this,99);
        this.m_onepassPackets = [];
        this.m_signaturePackets = [];
        while (packetToker.nextTag() == 4)
        {
            this.m_onepassPackets.push(packetToker.nextPacket());
        }
        this.m_literalPacket = packetToker.nextPacket();
        while (packetToker.nextTag() == 2)
        {
            this.m_signaturePackets.push(packetToker.nextPacket());
        }
    }
}});


PGPPacketTypes.MetaEncryptedPackett = PGPPacketTypes.AbstractPacket.extend(function(classref) { return {
    initialize: function(packetToker)
    {
        classref.superClass.initialize.call(this,109);
        this.sessionPublicKeyPackets = [];
        this.sessionSymmetricKeyPackets = [];
        while (packetToker.nextTag() == 1 || packetToker.nextTag() == 3)
        {
            if (packetToker.nextTag() == 1)
            {
                this.sessionPublicKeyPackets.push(packetToker.nextPacket());
            }
            else
            {
                this.sessionSymmetricKeyPackets.push(packetToker.nextPacket());
            }
        }
        this.encryptedPacket = packetToker.nextPacket();
    },

    tryToDecode: function(keyMap,passCache,sessionPackets,checkin,encryptedPacket)
    {
        var pwPendingSave = {};
        var retval = null;
        for (var ii = 0; ii < sessionPackets.length; ii++)
        {
            var sessKeyId = sessionPackets[ii].getKeyId();
//            lucidapi.debug("sessKeyId: %s", sessKeyId);
            for (var keyId in keyMap)
            {
                if (sessKeyId == "0000000000000000" || sessKeyId == keyId)
                {
                    if (checkin === (passCache.hasKeyId(keyId)))
                    {
                        try
                        {
                            var cryptoKey = keyMap[keyId]; // 
                            var keypassCouplet = pwPendingSave[keyId];
                            if (!keypassCouplet)
                            {
                                keypassCouplet = passCache.askForPassphrase(keyId,cryptoKey.mm_pubKeyAlgo, true);
                                pwPendingSave[keyId] = keypassCouplet;
                            }
                            var keypass = keypassCouplet[0];
                            var sessKey = sessionPackets[ii].decryptSessionKey(keypass,cryptoKey);
                            if (sessKey != null)
                            {
                                var symAlgo = sessKey.charCodeAt(0);
                                var realSessKey = sessKey.substr(1);
                                retval = encryptedPacket.getEncryptedPayload(realSessKey,symAlgo);
                                if (keypassCouplet[1])
                                    passCache.persistPassphrase(keyId,keypass);
                            }
                        }
                        catch (e)
                        {
                            Components.utils.reportError(e);
                        }
                        if (retval)
                            return retval;
                    }
                }
            }
        }
        return null;
    },

    getMetaEncryptedPayload: function(passCache,keyArray)
    {
        var keyMap = {}
        if (keyArray)
        {
            for (var kk = 0; kk < keyArray.length; kk++)
            {
    //            keyMap[keyArray[kk].getKeyId()] = keyArray[kk];
                var subKey = keyArray[kk].getCryptoKey();
                if (subKey)
                    keyMap[subKey.getKeyId()] = subKey;

            }
        }

        var sessPktsA = this.sessionPublicKeyPackets.concat();

        var retval = this.tryToDecode(keyMap,passCache,sessPktsA,true,this.encryptedPacket);
        if (retval == null)
        {
            retval = this.tryToDecode(keyMap,passCache,sessPktsA,false,this.encryptedPacket);
        }
        if (retval != null)
        {
            return retval;
        }
        throw lucidapi.createThrowable( "luciderr.noSessionKeysDecrypted" );
    }
}});

var PGPPacketTokenizer = lucidapi.AbstractClass.extend(function(classref) { return {
    initialize: function(s,depth)
    {
        classref.superClass.initialize.call(this);
//        lucidapi.debug("total length: %d", s.length);
        var packets = [];
        var nextIdx = 0;
        if (!depth)
            depth = 0;

        function visitRawPackets( s, handlerfunc )
        {
            /* handlerfunc( tag, s ) { return true; // to break off } */

            for (var ii = 0; ii < s.length;)
            {
                var tag = s.charCodeAt(ii++);
                if ((tag & 128) == 0)
                    break;

                var calclens = CodecUtils.pgp_parsePacketLength( s, tag, ii );
                var newpktstr = s.substr(calclens[1],calclens[2]);
                while (calclens[3])
                {
                    ii = calclens[1] + calclens[2];
                    calclens = CodecUtils.pgp_parsePacketLength( s, tag, ii );
                    newpktstr += s.substr(calclens[1],calclens[2]);
                }

//                lucidapi.debug("seeing packet %d: %s %d %d", depth, calclens[0], calclens[2], s.substr(calclens[1],calclens[2]).length);
                if (handlerfunc(calclens[0],newpktstr))
                {
                    break;
                };
                ii = calclens[1] + calclens[2];
            }
        }

        var handlerfunc = function(tag,packetdata)
        {
            switch (tag) {
                case 1:
                    packets.push( new PGPPacketTypes.SessionPublicKeyPacket(packetdata));
                    break;
                case 2:
                    packets.push( new PGPPacketTypes.SignaturePacket(packetdata));
                    break;
                case 3:
                    packets.push( new PGPPacketTypes.SessionSymmetricKeyPacket(packetdata));
                    break;
                case 5:
                case 7:
                    packets.push( new PGPPacketTypes.SecretKeyPacket(tag,packetdata));
                    break;
                case 6:
                case 14:
                    packets.push( new PGPPacketTypes.PublicKeyPacket(tag,packetdata));
                    break;
                case 8:
                    packets.push( new PGPPacketTypes.CompressedPacket(packetdata));
                    break;
                case 9:
                    packets.push( new PGPPacketTypes.EncryptedDataPacket(packetdata));
                    break;
                case 10:
                    // marker packet should be ignored
                    break;
                case 11:
                    packets.push( new PGPPacketTypes.LiteralPacket(packetdata));
                    break;
                case 18:
                    packets.push( new PGPPacketTypes.EncryptedIntegrityDataPacket(packetdata));
                    break;
                default:
                    packets.push( new PGPPacketTypes.StandardPacket(tag,packetdata));
                    break;
            }
        }

        visitRawPackets( s, handlerfunc );

        this.hasNext = function()
        {
            return (nextIdx < packets.length);
        }

        this.nextPacket = function()
        {
            if (this.hasNext())
                return packets[nextIdx++];
            return null;
        }

        this.nextTag = function()
        {
            if (this.hasNext())
                return packets[nextIdx].m_pktTag;
            return -1;
        }
    },
    nextObject: function()
    {
        return this.nextPacket();
    },

    visitObjects: function(handlers)
    {
        while (this.hasNext())
        {
            var obj = this.nextObject();
            var tag = obj.m_pktTag;

            var packetmapkey = tag.toString(16);
            if (packetmapkey.length < 2)
            {
                packetmapkey = "0" + packetmapkey;
            }
            packetmapkey = "0x" + packetmapkey;

            if (handlers[packetmapkey])
            {
                handlers[packetmapkey](obj);
            }
            else if (handlers["0xOO"])
            {
                handlers["0xOO"](obj);
            }
        }
    }

}});

PGPMetaTypes.AsymmetricKey = lucidapi.AbstractClass.extend(function(classref) { return {
    initialize: function(packet)
    {
        classref.superClass.initialize.call(this);
        this.mm_validityPeriod = 0;
        this.mm_type = packet.m_pktTag;
        this.mm_packet = packet;
        this.mm_keyFlags = 0;
        this.mm_pubKeyAlgo = packet.m_algo;
        this.mm_trailingpackets = [];
        if (packet.m_pktVersion == 3)
            this.mm_keyFlags = 0x0F;

        var tag = this.mm_packet.m_pktTag;
        if (tag == 5 || tag == 6)
        {
            this.subKeys = [];
        }
        if (packet.m_validity)
        {
            this.mm_validityPeriod = packet.m_validity * 24 * 60 * 60;
        }
    },

    isPrimaryKey: function()
    {
        return (this.mm_type == 5 || this.mm_type == 6);
    },

    isSecretKey: function()
    {
        return (this.mm_type == 5 || this.mm_type == 7);
    },
    getKeyTypeTag: function()
    {
        switch (this.mm_type) {
            case 5:
                return "sec";
                break;
            case 6:
                return "pub";
                break;
            case 7:
                return "ssb";
                break;
            case 14:
                return "sub";
                break;
            default:
                return "err";
        }
    },
    serialize: function(stripsecret)
    {
        var strippedBody = this.mm_packet.m_pktRawdata;
        var strippedTag = this.mm_packet.m_pktTag;
        if (stripsecret)
        {
            switch (strippedTag)
            {
                case 5:
                    strippedTag = 6;
                    break;
                case 7:
                    strippedTag = 14;
                    break;
            }
            strippedBody = strippedBody.substr(0,this.mm_packet.endingII);
        }
        var retval = CodecUtils.pgp_createPacketHeader(strippedTag,strippedBody.length) + strippedBody;
        for (var ii = 0; ii < this.mm_trailingpackets.length; ii++)
        {
            var trailer = this.mm_trailingpackets[ii];
            retval += trailer.serialize();
        }
        if (this.subKeys)
        {
            for (ii = 0; ii < this.subKeys.length; ii++)
            {
                var subKey = this.subKeys[ii];
                retval += subKey.serialize(stripsecret);
            }
        }
        return retval;
    },
    toString: function()
    {
        var retval = this.getKeyTypeTag();
        retval += ":";
        var trustFlag = "";
        if (this.mm_packet.m_pktTag == 6 || this.mm_packet.m_pktTag == 14)
        {
            trustFlag = "-";  // unknown (should be 'u' for ultimately trusted)
        }

        retval += trustFlag + ":";
        retval += this.getKeySize() + ":";
        retval += this.mm_packet["m_algo"] + ":";
        retval += this.getKeyId() + ":";
        retval += this.getCreatedStamp() + ":";
        retval += this.getExpirationStamp() + ":";
        retval += "::";
        retval += this.getUserId() + ":";
        retval += "::";
    //    lucdbg.alert("retval: " + retval);
        return retval;
    },

    getCreatedStamp: function()
    {
        var createdDate = new Date();
        createdDate.setTime(this.mm_packet.m_created * 1000);
        return pgp_calcDateString(createdDate);
    },

    getExpirationDateObject: function()
    {
        var retval = null;
        if (this.mm_validityPeriod > 0)
        {
            retval = new Date();
            retval.setTime((this.mm_packet.m_created + this.mm_validityPeriod) * 1000);
        }
        return retval;
    },

    getExpirationStamp: function()
    {
        var thedate = this.getExpirationDateObject();
        if (thedate)
        {
            thedate.setTime((this.mm_packet.m_created + this.mm_validityPeriod) * 1000);
            return pgp_calcDateString(thedate);
        }
        else
        {
            return ""; // never expires
        }
    },

    isExpired: function()
    {
        var keyExpi = this.getExpirationDateObject();
        if (keyExpi)
        {
            var maintenant = (new Date()).getTime();
            return (maintenant > keyExpi.getTime());
        }
        return false;
    },

    getUserId: function()
    {
        return this.userId;
    },

    getAlgoType: function()
    {
        return this.mm_packet.m_algo;
    },

    getKeyId: function()
    {
        return this.mm_packet.m_keyid;
    },

    addSubPacket: function(packet)
    {
        this.mm_trailingpackets.push(packet);
        if (packet.m_pktTag == 13)
        {
            this.userId = packet.m_pktRawdata;
        }
        else if (packet instanceof PGPPacketTypes.SignaturePacket)
        {
            this.mm_validityPeriod = packet.getKeyExpirationTime();
            var flags = packet.getKeyFlags();
            if (flags)
            {
        //        lucidapi.debug("flags %s", s2hex(flags));
                this.mm_keyFlags = flags.charCodeAt(0);
            }
    /*
            if (this.isPrimaryKey())
            {
                var hashdata = this.mm_packet.getPacketWithAltHeader();
                if (packet.m_pktVersion < 4)
                {
                    hashdata += this.getUserId();
                }
                else
                {
                    hashdata += sfromc(0xB4) + "\0\0\0" + sfromc(this.getUserId().length) + this.getUserId();
                }
                var verified = packet.verifySignature(hashdata,this.getMPIBlob());
                lucidapi.debug("verified: %s", verified);
            }
            else
            {
                var parentKey = this.m_parentKey;
                var subhashdata = "";
                subhashdata += parentKey.mm_packet.getPacketWithAltHeader();
                subhashdata += this.mm_packet.getPacketWithAltHeader();
                var subverified = packet.verifySignature(subhashdata,parentKey.getMPIBlob());
                lucidapi.debug("subverified: %s", subverified);
            }
    */
        }
    },
    addSubKey: function( subKey )
    {
        this.subKeys.push(subKey);
    },

    getSubKeys: function()
    {
        return this.subKeys;
    },
    getKeySize: function()
    {
        return pgp_calcMPILen(this.mm_packet.m_pktMpis[0]) * 8;
    },

    getCryptoKey: function()
    {
        var retval = null;
//        lucidapi.debug("keyflagsA: %d", this.mm_keyFlags );
        if ((this.mm_keyFlags & 0x0C) > 0)
        {
            retval = this;
        }
        else
        {
            var subKeys = this.getSubKeys();
            if (subKeys)
            {
                retval = this.getSubKeys()[0];
//                lucidapi.debug("keyflagsB: %d", retval.mm_keyFlags );
            }
        }
        return retval;
    },

    getMPIBlob: function()
    {
        return this.mm_packet.m_pktMpis;
    },

    getSecretS2K: function(s)
    {
        var retval = {
            s2k: { }
        };
        var ii = 0;
        var s2k = retval.s2k;
        s2k.usage = s.charCodeAt(ii++);
        if (s2k.usage > 0)
        {
            s2k.symAlgo = s2k.usage;
            s2k.mode = 0; // default fallback
            s2k.hashAlgo = 1; // default MD5
            if (s2k.usage >= 254)
            {
                s2k.symAlgo = s.charCodeAt(ii++);
                ii = CodecUtils.pgp_fillS2Kspec(s,ii,s2k);
            }
            var s2kIVsize = PGP_SYMMETRIC_BLOCK_SIZE[s2k.symAlgo];
            s2k.iv = s.substr(ii,s2kIVsize);
            ii += s2kIVsize;
        }
        retval.encryptedmpis = s.substr(ii);
        return retval;
    },

    getSecretBlob: function(passphrase)
    {
        var ii = 0;
        var secretdata = this.getSecretS2K(this.mm_packet.m_secretdata)
        var encryptedmpis = secretdata.encryptedmpis;
        var decmpis = encryptedmpis;
        var s2k = secretdata.s2k;
        if (s2k.usage > 0)
        {
            if (!passphrase)
                throw lucidapi.createThrowable( "luciderr.noPassphraseProvided" );
            key = CodecUtils.pgp_calcS2KOutput(s2k,passphrase);
            decmpis = pgp_decryptCFB(s2k.symAlgo, key, encryptedmpis, s2k.iv);
            decmpis = decmpis.substr(0,encryptedmpis.length);
        }
        var checksum = "foo";
        var calcsum = "bar";
        if (s2k.usage == 254)
        {
            checksum = decmpis.substr(decmpis.length - PGP_HASH_SIZE[2]);
            calcsum = pgp_hashString(2,decmpis.substr(0,decmpis.length - PGP_HASH_SIZE[2]));
        }
        else
        {
            if (this.mm_packet.m_pktVersion == 3)
            {
                checksum = (encryptedmpis.charCodeAt(encryptedmpis.length - 2) << 8) +
                            encryptedmpis.charCodeAt(encryptedmpis.length - 1);
            }
            else
            {
                checksum = (decmpis.charCodeAt(decmpis.length - 2) << 8) + decmpis.charCodeAt(decmpis.length - 1);
            }
            calcsum = 0;
            for (ii = 0; ii < decmpis.length - 2; ii++)
            {
                calcsum += decmpis.charCodeAt(ii);
            }
            calcsum = calcsum % 65536;
        }
        if (calcsum != checksum)
            throw lucidapi.createThrowable( "luciderr.decryptionChecksumMismatch" );
        ii = 0;
        var retval = this.getSecretBlobMPIs(decmpis);
        var pubmpis = this.getMPIBlob();
        for (var key in pubmpis)
        {
            retval[key] = pubmpis[key];
        }
        for (key = 0; key < pubmpis.length; key++)
        {
            retval.push(pubmpis[key]);
        }
        return retval;
    },

    getSecretBlobMPIs: function(decmpis)
    {
        throw lucidapi.createThrowable("luciderr.abstractMethod");
    }
}});

PGPMetaTypes.RSAKey = PGPMetaTypes.AsymmetricKey.extend(function(classref) { return {
    initialize: function(packet)
    {
        classref.superClass.initialize.call(this,packet);
    },
    getSecretBlobMPIs: function(decmpis)
    {
        var retval = [];
        var ii = 0;
        ii += pgp_insertAnMPI( retval, decmpis, ii );
        retval.rsa_d = retval[retval.length - 1];

        ii += pgp_insertAnMPI( retval, decmpis, ii );
        retval.rsa_p = retval[retval.length - 1];

        ii += pgp_insertAnMPI( retval, decmpis, ii );
        retval.rsa_q = retval[retval.length - 1];

        ii += pgp_insertAnMPI( retval, decmpis, ii );
        retval.rsa_u = retval[retval.length - 1];
        return retval;
    }
}});

PGPMetaTypes.PGPDSAKey = PGPMetaTypes.AsymmetricKey.extend(function(classref) { return {
    initialize: function(packet)
    {
        classref.superClass.initialize.call(this,packet);
    },
    getSecretBlobMPIs: function(decmpis)
    {
        var retval = [];
        var ii = 0;
        ii += pgp_insertAnMPI( retval, decmpis, ii );
        retval.dsa_x = retval[retval.length - 1];
        return retval;
    }
}});


PGPMetaTypes.PGPElGamalKey = PGPMetaTypes.AsymmetricKey.extend(function(classref) { return {
    initialize: function(packet)
    {
        classref.superClass.initialize.call(this,packet);
    },
    getSecretBlobMPIs: function(decmpis)
    {
        var retval = [];
        var ii = 0;
        ii += pgp_insertAnMPI( retval, decmpis, ii );
        retval.eg_x = retval[retval.length - 1];
        return retval;
    }
}});


PGPMetaTypes.createPublicKey = function(packet)
{
    switch (packet.m_algo)
    {
        case 1:
        case 2:
        case 3:
            return new PGPMetaTypes.RSAKey(packet);
        case 16:
        case 20:
            return new PGPMetaTypes.PGPElGamalKey(packet);
        case 17:
            return new PGPMetaTypes.PGPDSAKey(packet);
    }
    throw lucidapi.createThrowable("luciderr.unknownAsymmetricAlgorithm",packet.m_algo);
}

var SignatureGenerator = lucidapi.AbstractClass.extend(function(classref) { return {
    initialize: function(sigtype)
    {
        classref.superClass.initialize.call(this);
        if (!sigtype)
            sigtype = 0;
        this.m_sigType = sigtype;
        this.hashedpktList = [];
        this.unhashedpktList = [];
    },

    encodeSignature: function( text, mpis, pubkeyalgo )
    {

        var hashableSuffix = sfromc(4); // version

        hashableSuffix += sfromc(this.m_sigType); // text signed

        hashableSuffix += sfromc(pubkeyalgo); // pubkey algo
        hashableSuffix += sfromc(2); // hash algo

        var timeinsecs = new Date();

        var hashedpkts = CodecUtils.encodeSigTimeSubpacket(2,timeinsecs);

        for (var ii = 0; ii < this.hashedpktList.length; ii++)
        {
            hashedpkts += this.hashedpktList[ii];
        }

        hashableSuffix += pgp_encodeTwoOctetLength(hashedpkts.length); // hash packet len
        hashableSuffix += hashedpkts;

        var builder = hashableSuffix;

        var unhashedpkts = "";
        for (ii = 0; ii < this.unhashedpktList.length; ii++)
        {
            unhashedpkts += this.unhashedpktList[ii];
        }

        builder += pgp_encodeTwoOctetLength(unhashedpkts.length); // unhashed packet len
        builder += unhashedpkts;

        var trailer = sfromc(0x04) + sfromc(0xFF) + pgp_encodeFourOctetNumber(hashableSuffix.length);

        var hash = str_sha1(text + hashableSuffix + trailer);
        builder += hash.substr(0,2); // left-hand hash bytes

        if (pubkeyalgo == 17)
        {
            return CodecUtils.encodeDSASignature(mpis,builder,hash);
        }
        else
        {
            return CodecUtils.encodeRSASignature(mpis,builder,hash);
        }
    },

    addKeyFlags: function(flags)
    {
        var pktdata = sfromc(flags);
        this.hashedpktList.push(CodecUtils.pgp_createPacketHeader(27,pktdata.length,true) + pktdata);
    },

    addExpiration: function(seconds)
    {
        if (!seconds)
            seconds = 0;
        this.hashedpktList.push(CodecUtils.pgp_createPacketHeader(9,4,true) + pgp_encodeFourOctetNumber(seconds));
    },

    addPreferredSymmetricAlgorithms: function(octets)
    {
        this.hashedpktList.push(CodecUtils.pgp_createPacketHeader(11,octets.length,true) + octets);
    },

    addPreferredHashAlgorithms: function(octets)
    {
        this.hashedpktList.push(CodecUtils.pgp_createPacketHeader(21,octets.length,true) + octets);
    },

    addPreferredCompressionAlgorithms: function(octets)
    {
        this.hashedpktList.push(CodecUtils.pgp_createPacketHeader(22,octets.length,true) + octets);
    },

    addFeatures: function(octets)
    {
        this.hashedpktList.push(CodecUtils.pgp_createPacketHeader(30,octets.length,true) + octets);
    },

    addIssuer: function(rawkeyid)
    {
        var keyid = hex2s(rawkeyid);
        this.unhashedpktList.push(CodecUtils.pgp_createPacketHeader(16,keyid.length,true) + keyid);
    }
}});

function makeRandomLessThan(bigmpi)
{
    var randn = bigmpi.substr(0,3) + randomString(bigmpi.length - 3);
    var randb = mpi2b(randn);
    var bignum = mpi2b(bigmpi);
    while (bsub(bignum,randb).length == 0)
    {
        randb = bdiv(randb,[2]).q;
    }

    randn = b2mpi(randb);
    return randn;
}

var PGPAbstractKeyGenerator = lucidapi.AbstractClass.extend(function(classref) { return {
    initialize: function(keysize)
    {
        classref.superClass.initialize.call(this);
        this.keysize = keysize;
    }
}});

PGPAbstractKeyGenerator.prototype.encodePacket = function(tag,passphrase)
{
    var secret = (tag == 5 || tag == 7);
    var body = this.encodePublicPortion();
    if (secret)
        body += this.encodePrivatePortion(passphrase);
//    var header = CodecUtils.pgp_createPacketHeader( tag, body.length );
//    return header + body;
    return body;
}

PGPAbstractKeyGenerator.prototype.encodePublicPortion = function()
{
    var retval = sfromc(4); // packet version

    var timeinsecs = Math.round((new Date()).getTime() / 1000); // created
    retval += sfromc((timeinsecs >> 24) & 0xFF );
    retval += sfromc((timeinsecs >> 16) & 0xFF );
    retval += sfromc((timeinsecs >> 8) & 0xFF );
    retval += sfromc((timeinsecs) & 0xFF );

    retval += sfromc(this.getKeyAlgorithm());

    retval += this.getPublicMPIs();
    return retval;
}

PGPAbstractKeyGenerator.prototype.encodePrivatePortion = function(passphrase)
{
    var calcsum = 0;
    if (passphrase)
    {
        var key = pgp_hashString(1,passphrase).substr(0,16);
        var s2kiv = hex2s("ffffffffffffffffffffffffffffffff"); // iv
        var decmpis = this.getPrivateMPIs();
        for (var ii = 0; ii < decmpis.length; ii++)
        {
            calcsum += decmpis.charCodeAt(ii);
        }
        calcsum = calcsum % 65536;
        decmpis += sfromc(calcsum >> 8) + sfromc(calcsum % 256);
        var encmpis = pgp_encryptString(7,decmpis,false,key,s2kiv);
        encmpis = encmpis.substr(0,decmpis.length);
        var s2k = sfromc(7); // AES128
        s2k += s2kiv;
        return s2k + encmpis;
    }
    else
    {
        var retval = this.getPrivateMPIs();
        for (ii = 0; ii < retval.length; ii++)
        {
            calcsum += retval.charCodeAt(ii);
        }
        calcsum = calcsum % 65536;
        retval = sfromc(0) + retval; // s2k usage
        retval += sfromc(calcsum >> 8) + sfromc(calcsum % 256);
        return retval;
    }
}

var PGPRSAKeyGenerator = PGPAbstractKeyGenerator.extend(function(classref) { return {
    initialize: function(keysize)
    {
        classref.superClass.initialize.call(this,keysize);
        this.mpiE = hex2s("0011010001");
    //    lucidapi.debug("test %s", s2hex(b2mpi(t2b(hex2s("010001")))));
    //    this.mpiP = hex2s("0200f0b8718d6a6402080662c505fecac834c299e13399dc2f8fc48b9855c98a74fff4bed2366c0b3de06e70b32d702c2d8d0e4bc994760f719c011c993896eac9bb");
    //    var locP = mpi2b(this.mpiP);
    //    this.mpiQ = hex2s("0200f5616487da5e84bd96170e1db32718e9d4888fb23c591e82af70c4af62c16daa6b509c0522b6bf51e45668d03292a0200043a80a476422690e26ddd4b4ca36f3");
    //    var locQ = mpi2b(this.mpiQ);

        var locP = mpp(keysize);
        this.mpiP = b2mpi(locP);
        var locQ = mpp(keysize);
        this.mpiQ = b2mpi(locQ);

        if (s2hex(b2mpi(bsub(locQ,locP))) == "0000")
        {
            var locTmp = locQ;
            locQ = locP;
            locP = locTmp;
        }


        var locP1 = bsub(locP,[1]);
        var locQ1 = bsub(locQ,[1]);
        var locPQ = bmul(locP1,locQ1);
        var locE = mpi2b(this.mpiE);

        var locN = bmul(locP,locQ);
        this.mpiN = b2mpi(locN);

        var locD = modinverse(locE,locPQ);
        this.mpiD = b2mpi(locD);

        var locU = modinverse(locP,locQ);
        this.mpiU = b2mpi(locU);
    }
}});

PGPRSAKeyGenerator.prototype.getKeyAlgorithm = function()
{
    return 1;
}

PGPRSAKeyGenerator.prototype.getPublicMPIs = function()
{
    return this.mpiN + this.mpiE;
}

PGPRSAKeyGenerator.prototype.getPrivateMPIs = function()
{
    return this.mpiD + this.mpiP + this.mpiQ + this.mpiU;
}

/*
PGPRSAKeyGenerator.prototype.encodePublicPortion = function()
{
    var retval = sfromc(3); // packet version

    var timeinsecs = Math.round((new Date()).getTime() / 1000); // created
    retval += sfromc((timeinsecs >> 24) & 0xFF );
    retval += sfromc((timeinsecs >> 16) & 0xFF );
    retval += sfromc((timeinsecs >> 8) & 0xFF );
    retval += sfromc((timeinsecs) & 0xFF );

    var validityDays = 365;
    retval += sfromc((validityDays >> 8) & 0xFF );
    retval += sfromc((validityDays) & 0xFF );

    retval += sfromc(this.getKeyAlgorithm());

    retval += this.getPublicMPIs();
    return retval;
}
*/

var PGPDSAKeyGenerator = PGPAbstractKeyGenerator.extend(function(classref) { return {
    initialize: function(keysize)
    {
        classref.superClass.initialize.call(this,keysize);

        switch (keysize) {
            case 512:
                this.mpiP = hex2s("0200b833b9c6443b6fc22b5c894051951ca0a55b17afc908807ba9aaa22f0e58e53b590b24792d3ffbd945df4f9c3ea48324fffb3da2e792bffb0329fa9485d912d7");
                this.mpiQ = hex2s("00a0d40c71c91852fb31cd96075ef48b2ab2d82df401");
                this.mpiG = hex2s("01fe2e010831c8588564ac65d72471da20d08804250cb5e263a558d04cfc22c31bd29b7b8fbbf38ed4f5dd8e5ef09dbded68c84a0bdf9156acca9a176c32d23e4976");
                break;
            case 768:
                this.mpiP = hex2s("0300e5de5bec24ed3325439fb08333a58840992ed57fed3bd2081f9ec681de8c10917a8802fb9491a87b21af0a1fa9f622d0e9de689357b9c17f81a811533b0ea7a5f810a1bdd00d4369cb241297d1637b1dec114663de9c6b6e574790e0504e16c3");
                this.mpiQ = hex2s("00a09d3f8f9e3e3dbece39944f97c03ab418e8b1fef5");
                this.mpiG = hex2s("02fd1cc7e9f749bb54897d5e42c661ffc7a1884efbc4a05e264a17c0f353bdf75be70701e12b5e365f2938187d4d1a1926da5718774f64ce38e5cb6b35f8fa6a6b81bbe22f4559b49986109dc6a8a6586d5d04cbfe1a5ea7007c423a6f59c5cd40c5");
                break;
            case 1024:
                this.mpiP = hex2s("040096ef096c1649292c8715e01158cc8c67586158950c46556183f8725b1367949c160434c6b51178842d9a8c6651258fbfe49a5efefe5515aa0b0062d61db722998b4daa0a315ae9d773a9ababcdc29bc430e7b4c2ce8b35c937a63d4f24f44017860067b89e9ff9623d400070c7b0fcf31a2648169c9f79560eeb86d1f75d620b");
                this.mpiQ = hex2s("00a090194c281f0a08bf51e7a464e44873a92a70aa95");
                this.mpiG = hex2s("040092f4b4e8357d9928d75aa80539229bcb8507ba07b0057977e34371e9783af657fd1f14325bb5df8c14279c41d9fb57d6bf23de687b4ba0d83bf0d9f99929f6068877932e84a118eaed830cf59e0470bebaa06b4cb1f199ce6af639f8c1fc8a385bd198d1740d2dfb32ac3c18615bba3837b492d4911610d9418160b7524b9166");
                break;
            default:
                throw lucidapi.createThrowable("luciderr.badKeySize", keysize);
        }


        var locP = mpi2b(this.mpiP);
    //    var locQ = mpi2b(mpiQ);
        var locG = mpi2b(this.mpiG);

        this.mpiX = makeRandomLessThan(this.mpiQ);
        var locY = bmodexp(locG,mpi2b(this.mpiX),locP);
        this.mpiY = b2mpi(locY);
    }
}});

PGPDSAKeyGenerator.prototype.getKeyAlgorithm = function()
{
    return 17;
}

PGPDSAKeyGenerator.prototype.getPublicMPIs = function()
{
    return this.mpiP + this.mpiQ + this.mpiG + this.mpiY;
}

PGPDSAKeyGenerator.prototype.getPrivateMPIs = function()
{
    return this.mpiX;
}

var PGPElGamalKeyGenerator = PGPAbstractKeyGenerator.extend(function(classref) { return {
    initialize: function(keysize)
    {
        classref.superClass.initialize.call(this,keysize);

        switch (keysize) {
            case 512:
                this.mpiG = hex2s("000305");
                this.mpiP = hex2s("0200b6d6a6a6e5550919b84cf71d62f1ca8c4632a330e29f243f7a7a472d329c3cd7" +
                                  "0f885fff8fc22c9332caf66b8b9254f6db75886dc88fb4d9759c3fb8441c0c6f");
                break;
            case 768:
                this.mpiG = hex2s("000305");
                this.mpiP = hex2s("030088f0e2ec01653b1aca759cbe1256c1d7a0a34e6a2eb7bcfa0ba5cc12d39f2ca1" +
                                  "c5e7597a899d46f94610e51c17917f387f84e616cc16142bd3b65112fea88350" +
                                  "e7659a89c7d54a9efa6578174f9961be01c0b6c975174c673ba78305e2dfc267");
                break;
            case 1024:
                this.mpiG = hex2s("000305");
                this.mpiP = hex2s("0400c865be325ecb4f26c9c69cf666b8854ca006a7c755a88df650d8daf9c2d437c3" +
                                  "87a31d4646147a19914b5f450ca1e1792c9beaebfa42fc7007819b82820040fc" +
                                  "178d3de2f06f916a79876a1b189f800d6b44b0b950dc16b07e03ff5d675d5786" +
                                  "6a00c4e35d9a36892b438f7f41d1bd1b06ff83a4119437402d23086bc0c8a217");
                break;
            case 2048:
                this.mpiG = hex2s("000305");
                this.mpiP = hex2s("" +
                                  "080095a9fd8267756f08c4c5d9b079ec3882eba0320312f1003599ff04851219f8a41888e7" +
                                  "623a6996956b3047054e346538d4d3ec6ad961647d2f8b445bee2dacda70ab436999d30878526ffe" +
                                  "62323ac494dcdc01f1761e895dc098d65be1c77145b2946e13d34fd138296806863e8aede9270e65" +
                                  "71ac5897efedf4eb93c925d1ad920fff16ae2fd52b075289f37eab6e26692b20cdf07197abd36091" +
                                  "ad128790bd65ef8cf496bf3a06c42759e56e4ba6a89f29f2b96d410ffe8f168228536b8fa0577d89" +
                                  "6d51e1f77c7cc6888705392830621fb974b8567aee3adad4f1abd51922b0d65332474ecce9495a4d" +
                                  "852feed751024f5ef52e53825587d6c6c5e6521083" +
                                  "");
                break;
            case 3072:
                this.mpiG = hex2s("000306");
                this.mpiP = hex2s("" +
                                  "0c00a9f54ad3081f70f16a36acb41574e1b584c13a6b4607259d647be7dfe16a69574e2169" +
                                  "507190a003995c24540ced7a425efcf0e2796ea2f284b462b612da6dc6b644530b4f5c50ecb8c853" +
                                  "c2ccf638d765537ee621e58f6d03024d87e1e26e02668d5ff54c66330230ce2524b79ca4d5c6fb34" +
                                  "740072b869683f20603c5238b746065826a0936a344afacec16fe26e5f7a8c74d62ba8e61d927572" +
                                  "4197d55c304ca98357dafa437b9e903164f1135ce1e6baf30f40638592240ef6198f2b85699ffa9c" +
                                  "429569a3bbbf0740f2e9b0130d444bc83d34abd72f88e968fc9fb9447bb5a6bf6c6e87b79df93fb7" +
                                  "aa1d500b1164d882230ae1ff056735349e2c8eaed156ba4fa9f5aad9e63dba076319d1ccc2fb8215" +
                                  "ada4ad8dd9b52db76cf45c50b742dc7ecd4d71167547fb2e042247351e19441a45d1b1a08bd192cc" +
                                  "08049c1ed32b452f12d061306addc8dfb72964f882472bf913967842a443646bda86705ace313dde" +
                                  "5a1715d7f050edc15d2647ee764e57311505bc347d92e291f8abfe6763" +
                                  "");
                break;
            case 4096:
                this.mpiG = hex2s("000306");
                this.mpiP = hex2s("" +
                                  "10009251cc44458ce87c8c63e0cfe7ba6479968d6cb9c053144340647b0b69172002546805" +
                                  "53f9c6221f20a53d83772da88d438e01cdbd747df8f451ac82b2e45ccbd8fe79a21371fd056c24ce" +
                                  "abcbfedcd8ba153bce864710a811c9e70e9a25cf403297931469766790f1a02e94a33a521a7a5f8e" +
                                  "4b0aa07b21824e4b451a3bbb697ced34b34ffbcc1cb035b8306095d65fb384ab700e7a2598a6d0de" +
                                  "14574fe3485be0dd481f4c8af08dbd17c9aa5484edfbcf85e5182723ad28a9a6616322c5253e0f44" +
                                  "082c12509c3e62e23bf1002bd0327acec946fb94e499ac19185f49dd6443bc083c1888bc514260eb" +
                                  "63106a2ee65b1bedfd783a141060814c5f509bfccabc2e19eb2b4f4be7e41d54f12c2b402ec12938" +
                                  "3f64917eab6a56dd4a226ad564bace0aea39e87327a15f5d80db9033329d16ac658657812a794ced" +
                                  "6fa9ede82d53dd52df38e0cde21834fdf1448b3a7462013e4e0f51f7bec53258ac6fdf8c7580189b" +
                                  "718fa7d54fdb54f9f1f8372e8bc10e50a30ee3c2fb291b1c8cce715be6153c9d5696792acad0c6c4" +
                                  "d9369fe0cb74a7a9008b6a3e0c6b3df1e2d92a231786e4ca706408dc25ca4e273ac9b07612f17f04" +
                                  "ff45a1b44248ceacc5955ad228d5f99e9626b4d8acedddf08c4192acfe4bdaa014e71dbeaf717f57" +
                                  "39bbb9d7d02035c30cb595128beecaf45fadc3c61d4cbe469a3134f82afc92851af8c9e2a3" +
                                  "");
                break;
            default:
                throw lucidapi.createThrowable("luciderr.badKeySize", keysize);
        }
        var locG = mpi2b(this.mpiG);
        var locP = mpi2b(this.mpiP);
        
//        var locP = mpp(keysize);
//        this.mpiP = b2mpi(locP);


        this.mpiX = makeRandomLessThan(this.mpiP);
        var locY = bmodexp(locG,mpi2b(this.mpiX),locP);
        this.mpiY = b2mpi(locY);
    }
}});

PGPElGamalKeyGenerator.prototype.getKeyAlgorithm = function()
{
    return 16;
}

PGPElGamalKeyGenerator.prototype.getPublicMPIs = function()
{
    return this.mpiP + this.mpiG + this.mpiY;
}

PGPElGamalKeyGenerator.prototype.getPrivateMPIs = function()
{
    return this.mpiX;
}

function pgp_filterKeyList( keyArray, keyIds )
{
    if (keyIds && keyArray)
    {
        if (typeof(keyIds) == "string")
            keyIds = [keyIds];
        var newKeyArray = [];
        var ii, key;
        for (ii = 0; ii < keyArray.length; ii++)
        {
            key = keyArray[ii];
            for (var jj = 0; jj < keyIds.length; jj++)
            {
                if (key.getKeyId() == keyIds[jj])
                    newKeyArray.push(key);
            }
        }
        return newKeyArray;
    }
    return [];
}


var PGPExtendedTokenizer = PGPPacketTokenizer.extend(function(classref) { return{
    initialize: function(s,depth)
    {
        classref.superClass.initialize.call(this,s,depth);
    },
    nextObject: function()
    {
        var nextTag = this.nextTag();
        switch (nextTag) {
            case 1:
            case 3:
                return new PGPPacketTypes.MetaEncryptedPackett( this );
            case 4:
                return new PGPPacketTypes.MetaSignedPacket( this );
            default:
                return this.nextPacket();
        }
    }
}});


function PGPResult(data,siginfo,format,filename)
{
    this.m_result = data;
    this.m_siginfo = siginfo;
    this.m_format = format;
    this.m_filename = filename;
};

PGPResult.prototype.toString = function()
{
    return "" + this.m_result;
};


function pgp_encryptDataHelper( keyArray, data )
{
    var symAlg = 7;          // AES=7, AES192=8, AES256=9
    var kSize = [16,24,32]  // key length in bytes

    var keylen = kSize[symAlg - 7];  // session key length in bytes

    var sesskey = randomString(keylen, 0);

    var seddata = CodecUtils.encodeEncryptedIntegrityProtectedData( symAlg, sesskey, data);

    var retval = "";

    for (var ii = 0; ii < keyArray.length; ii++)
    {
        var cryptoKey = keyArray[ii].getCryptoKey();
        retval += CodecUtils.encodePublicKeyEncryptedSession(cryptoKey, symAlg, sesskey);
    }
    retval += seddata;
    return retval;
};

function pgp_loadKeyChain( data )
{
    data = pgp_armorStrip( data );
    var retval = [];
    var toker = new PGPPacketTokenizer(data);
    var lastKey = null;
    var loop = true;
    while (loop)
    {
        var nextTag = toker.nextTag();
        switch (nextTag) {
            case 13:
                lastKey.addSubPacket(toker.nextPacket());
                break;
            case 2:
                lastKey.addSubPacket(toker.nextPacket());
                break;
            case 12:
                toker.nextPacket();
                break;
            case 5:
            case 6:
                lastKey = PGPMetaTypes.createPublicKey(toker.nextPacket());
                retval.push(lastKey);
                break;
            case 7:
            case 14:
                lastKey = PGPMetaTypes.createPublicKey(toker.nextPacket());
                retval[retval.length -1].addSubKey( lastKey );
                lastKey.m_parentKey = retval[retval.length -1];
                break;
            default:
                loop = false;
                break;
        }
    }

    return retval;
};

function pgp_getKeyList( data /*, includesub */ )
{
    var chain = pgp_loadKeyChain(data);
    var retval = "";
    for (var ii = 0; ii < chain.length; ii++)
    {
        if (ii > 0)
            retval  += "|";
        retval  += chain[ii].toString();
    }
    return retval;
};

function pgp_createKeyMap(keyArray)
{
    var keyMap = {}
    if (keyArray)
    {
        for (var kk = 0; kk < keyArray.length; kk++)
        {
            keyMap[keyArray[kk].getKeyId()] = keyArray[kk];
            var subKey = keyArray[kk].getCryptoKey();
            if (subKey)
                keyMap[subKey.getKeyId()] = subKey;
        }
    }
    return keyMap;
}

function pgp_decryptData( origdata, keyset, options )
{
    var passphrase_callback = options.passCache;

    var pubKeyMap = pgp_createKeyMap(keyset.getPublicChain());

    if (typeof(passphrase_callback) == "string")
        lucidapi.error("bad callback");
    var keyArray = keyset.getSecretChain();
    var data = origdata;
    data = pgp_armorStrip( data );
    var msg = "";
    var handlers = {};
    var sig = null;
    var format = 't';
    var filename = "";

    handlers["0x00"] = function( /* packet */ )
    {
    }
    handlers["0x02"] = function( packet )
    {
        var sigKeyId = packet.getIssuer();
        sig = {
            keyid: sigKeyId
        }
        if (pubKeyMap && pubKeyMap[sigKeyId])
        {
            var cstext = pgp_armorExtractClearsignedText(origdata);
//            lucidapi.debug("cstextvlen: %d", cstext.length)
//            lucidapi.debug("vsigning: %s", s2hex(cstext));
//            var verified = pgp_checkSignature(cstext,pubKeyMap[sigKeyId].getMPIBlob(),packet);
            var verified = packet.verifySignature(cstext,pubKeyMap[sigKeyId].getMPIBlob());
//            sig.verified = verified;
//            lucidapi.debug( "verified: %s", verified);
            sig.verified = verified;
        }
    }
    handlers["0x08"] = function( packet )
    {
        var subToker = new PGPExtendedTokenizer(packet.getCompressedPayload());
        subToker.visitObjects(handlers);
    }
    handlers["0x0b"] = function( packet )
    {
        msg = packet.m_payload;
        format = packet.m_format;
        filename = packet.m_filename;
    }
    handlers["0x63"] = function( packet )
    {
        msg = packet.m_literalPacket.m_payload;
        format = packet.m_literalPacket.m_format;
        filename = packet.m_literalPacket.m_filename;
        if (packet.m_signaturePackets.length > 0)
        {
            var sigPacket = packet.m_signaturePackets[0];
            var sigKeyId = sigPacket.getIssuer();
            sig = {
                keyid: sigKeyId
            }
            if (msg && pubKeyMap && pubKeyMap[sigKeyId])
            {
                var verified = packet.m_signaturePackets[0].verifySignature(msg,pubKeyMap[sigKeyId].getMPIBlob());
                sig.verified = verified;
            }
        }
    }
    handlers["0x6d"] = function( packet )
    {
        var subToker = new PGPExtendedTokenizer(packet.getMetaEncryptedPayload(passphrase_callback, keyArray));
        subToker.visitObjects(handlers);
    }

//    lucidapi.debug("beginning decrypt!");
    var toker = new PGPExtendedTokenizer(data);
    toker.visitObjects(handlers);

    if (options.verifyOnly)
    {
        return new PGPResult( null, sig, format, filename );
    }
    else
    {
        return new PGPResult( msg, sig, format, filename );
    }
};

function pgp_clearsignData(data,keyArray,signkey,passCache)
{
    var prepdata = pgp_normalizeEOL(data);

    var sigKey = keyArray[0];
    var myarg1 = sigKey.getKeyId();

    var keypassCouplet = passCache.askForPassphrase(myarg1,sigKey.getAlgoType(), true);
    var keypass = keypassCouplet[0];

    var mpis = sigKey.getSecretBlob(keypass);

    if (keypassCouplet[1])
        passCache.persistPassphrase(myarg1,keypass);

    var ciphertext = CodecUtils.encodeSignature( prepdata, signkey, mpis, 1, sigKey.getAlgoType() );

    var sigtext = pgp_armorWrap(ciphertext, "SIGNATURE" );

    var retval = "";
    retval += "-----BEGIN PGP SIGNED MESSAGE-----\n";
    retval += "Hash: SHA1\n\n";
    retval += prepdata + "\n";
    retval += sigtext;
    return new PGPResult( retval );
};

function pgp_encryptData( data, keyset, options )
{
    if (options)
    {
        var recipientIds = options.encKeyIds;
        var signerId = options.sigKeyIds;
        var clearsign = options.clearsign;
        var filename = options.filename;
    }

    if (recipientIds)
    {
        if (recipientIds.length > 0)
            recipientIds = recipientIds.split(" ");
    }
    else
    {
        recipientIds = [];
    }

    var seckeyArray = keyset.getSecretChain();
    seckeyArray = pgp_filterKeyList( seckeyArray, signerId );

    if (clearsign && seckeyArray.length > 0)
    {
        return pgp_clearsignData(data,seckeyArray,signerId,options.passCache);
    }
    else
    {
        var keyArray = keyset.getPublicChain();
        lucidapi.assert(recipientIds instanceof Array);
        keyArray = pgp_filterKeyList(keyArray, recipientIds);

        var format = options.format;
        if (!format)
            format = 'b';

        switch (format) {
            case 't':
                data = pgp_normalizeEOL(data);
                break;
            case 'b':
                break;
        }

        var outtext = CodecUtils.encodeLiteralData(data,format,filename);

        if (seckeyArray && seckeyArray.length > 0)
        {
            var sigType = 0x00;
            if (format == 't')
                sigType = 0x01;
            var sigKey = seckeyArray[0];

            var passCache = options.passCache;
            var keypassCouplet = passCache.askForPassphrase(signerId, sigKey.getAlgoType(), true);
            var keypass = keypassCouplet[0];
            var mpis = seckeyArray[0].getSecretBlob(keypass);
            if (keypassCouplet[1])
                passCache.persistPassphrase(signerId,keypass);
            var onepass = CodecUtils.onePassSignature( signerId, sigType, sigKey.getAlgoType() );
            outtext += CodecUtils.encodeSignature( data, signerId, mpis, sigType, sigKey.getAlgoType() );
            outtext = onepass + outtext;
        }

        outtext = CodecUtils.encodeCompressedData( 1, outtext );
        lucidapi.assert(keyArray instanceof Array);
        if (keyArray.length > 0)
            outtext = pgp_encryptDataHelper( keyArray, outtext );
        outtext = pgp_armorWrap(outtext, "MESSAGE" );
        return new PGPResult( outtext );
    }
};

function pgp_generateKeys(userid,passphrase,keysize,subsize,expiresecs)
{
    var topkeypktgen = new PGPDSAKeyGenerator(keysize);
    var pktbody = topkeypktgen.encodePacket(5,passphrase);
    var pkthdr = CodecUtils.pgp_createPacketHeader( 5, pktbody.length );
    var pktdata = pkthdr + pktbody;
    var parsedPkt = new PGPPacketTypes.SecretKeyPacket(5,pktbody);
    var parsedKey = new PGPMetaTypes.PGPDSAKey(parsedPkt);

    var retval = pktdata;
    retval += CodecUtils.pgp_createPacketHeader( 13, userid.length ) + userid;

    var hashdata = parsedPkt.getPacketWithAltHeader();

    var sigVer = 4;

    var topkeysig = null;
    if (sigVer == 3)
    {
        hashdata += userid;
        topkeysig = CodecUtils.encodeSignature( hashdata, parsedKey.getKeyId(), parsedKey.getSecretBlob(passphrase), 19, topkeypktgen.getKeyAlgorithm() );
    }
    else
    {
//        hashdata += sfromc(0xB4) + "\0\0\0" + sfromc(userid.length) + userid;
        hashdata += sfromc(0xB4) + pgp_encodeFourOctetNumber(userid.length) + userid;
        var siggen = new SignatureGenerator(19);
        siggen.addKeyFlags(0x20 | 0x02 | 0x01);
        siggen.addExpiration(expiresecs);
        siggen.addPreferredSymmetricAlgorithms(hex2s("07"));
        siggen.addPreferredHashAlgorithms(hex2s("02"));
        siggen.addPreferredCompressionAlgorithms(hex2s("01"));
        siggen.addFeatures(hex2s("01"));
        siggen.addIssuer(parsedKey.getKeyId());
        topkeysig = siggen.encodeSignature( hashdata, parsedKey.getSecretBlob(passphrase), topkeypktgen.getKeyAlgorithm() );
    }


    retval += topkeysig;

    var dosub = true;

    if (dosub)
    {
        var subkeypktgen = new PGPElGamalKeyGenerator(subsize);
        var subpktbody = subkeypktgen.encodePacket(7,passphrase);
        var subpkthdr = CodecUtils.pgp_createPacketHeader( 7, subpktbody.length );
        var subpktdata = subpkthdr + subpktbody;
        var subparsedPkt = new PGPPacketTypes.SecretKeyPacket(7,subpktbody);
//        var subparsedKey = new PGPElGamalKey(subparsedPkt);

        retval += subpktdata;
        hashdata = parsedPkt.getPacketWithAltHeader();
        hashdata += subparsedPkt.getPacketWithAltHeader();
        siggen = new SignatureGenerator(24);
        siggen.addKeyFlags(0x08 | 0x04);
        siggen.addExpiration(expiresecs);
        siggen.addIssuer(parsedKey.getKeyId());
        var subkeysig = siggen.encodeSignature( hashdata, parsedKey.getSecretBlob(passphrase), topkeypktgen.getKeyAlgorithm() );
        retval += subkeysig;
    }

//    retval += subkeypktgen.encodePacket(7);
    retval = pgp_armorWrap(retval, "PRIVATE KEY BLOCK" );
    return retval;
}

